innodb_ruby:窥探InnoDB奥秘的神器
一个InnoDB文件的解析工具,通过解析InnoDB文件,可以窥探其中很多的奥秘,绝对是一款非常优秀,高逼格的研究InnoDB的工具。但是作者不建议在生产环境使用,因为肯定有BUG,O(∩_∩)O哈哈~,作者写这个工具的主要目的是作为一个学习研究InnoDB的工具。
github地址
Github地址:https://github.com/jeremycole/innodb_ruby
安装
安装比较简单,执行命令gem install innodb_ruby
即可。安装完成后,执行如下命令验证innodb_ruby是否安装成功:
[afei@afei app]# innodb_space --help
Usage: innodb_space <options> <mode>
Invocation examples:
innodb_space -s ibdata1 [-T tname [-I iname]] [options] <mode>
... ...
如果提示ruby版本过低,从淘宝ruby镜像中下载高版本编译安装即可:
镜像地址:https://ruby.taobao.org/mirrors/ruby/ruby-1.9.3-p551.tar.gz
./configure --prefix=/app/ruby-2.2.10
make && make install
建议安装1.9.3-p551版本,因为当我安装了2.2.10版本的ruby后,执行innodb_space还是得到这样的错误信息:/usr/lib/ruby/gems/1.8/gems/bindata-2.4.3/lib/bindata.rb:5: BinData requires ruby >= 1.9.3。相关issue地址:https://github.com/jeremycole/innodb_ruby/issues/40。另外,安装ruby时,一步一步来,确保make以及make install时没有任何error信息。
准备数据
创建表插入数据之前,检查MySQL环境,MySQL建议5.5以上的版本,并且属性值:innodb_file_per_table=ON,innodb_file_format=Barracuda。接下来创建一张表,并借助存储过程插入一些数据:
-- 创建表
DROP TABLE IF EXISTS t_afei;
CREATE TABLE t_afei (
id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
num int not null
) ENGINE=InnoDB;
-- 创建存储过程
DROP PROCEDURE IF EXISTS insertbatch;
CREATE PROCEDURE insertbatch()
BEGIN
DECLARE i INT;
SET i=1;
WHILE(i<=1000000) DO
INSERT INTO yyfax_afei.t_afei(num)VALUES(i);
SET i=i+1;
END WHILE;
END;
-- 调用存储过程
call insertbatch();
用法示例
查看索引信息--name为索引名称,fseg为leaf表示属于叶子页的segment:
[
id name root fseg used allocated fill_factor
2405 PRIMARY 3 internal 3 3 100.00%
2405 PRIMARY 3 leaf 1743 1760 99.03%
]命令中的/data/mysql是mysql的datadir,yyfax_afei是数据库名称,t_afei是表名称。建议切换到${datadir}目录下,那么执行命令时-T的值为
yyfax_afei/t_afei
即可。
index-level-summary
得到指定level的所有page信息:
# level=0的page太多,所以只统计行数:
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/t_afei -I PRIMARY -l 0 index-level-summary | wc -l
1744
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/t_afei -I PRIMARY -l 1 index-level-summary
page index level data free records min_key
36 2405 1 7813 8139 601 id=1
37 2405 1 14846 838 1142 id=344688
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/t_afei -I PRIMARY -l 2 index-level-summary
page index level data free records min_key
3 2405 2 26 16226 2 id=1
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/t_afei -I PRIMARY -l 3 index-level-summary
page index level data free records min_key
我们知道level值和索引树的高度是强相关的(叶子节点的level都是0),所以通过这个命令也可以知道InnoDB索引树高度。由上面执行命令的结果可知,level=3时没有任何数据,而level等于1和2都有,所以示例100w数据的表的索引树高度是3。
space-page-type-regions
统计相同类型页的连续空间,如下所示,start/end表示起始页,count总计占的页数。
[afei@afei mysql]# innodb_space -f /data/mysql/yyfax_afei/t_afei.ibd space-page-type-regions
start end count type
0 0 1 FSP_HDR
1 1 1 IBUF_BITMAP
2 2 1 INODE
3 37 35 INDEX
38 63 26 FREE (ALLOCATED)
64 1774 1711 INDEX
1775 1919 145 FREE (ALLOCATED)
通过结果可知,page为0,1,2类型名称分别是:FSP_HDR, IBUF_BITMAP, INODE。从page=3开始才是存放行数据和指针的页。
index-recurse
递归一个索引需要依赖一个ruby脚本文件simple_t_describer.rb,脚本内容如下:
class SimpleTDescriber < Innodb::RecordDescriber
type :clustered
key "i", :INT, :UNSIGNED, :NOT_NULL
end
执行如下命令:
innodb_space -f /data/mysql/yyfax_afei/t_afei.ibd -r ~/simple_t_describer.rb -d SimpleTDescriber -p 3 index-recurse > recurseindex.log
这条命令会从root开始,全表扫描,以升序的方式遍历整个B+Tree索引树,遍历过程中会输出每个page以及指针的信息,包括叶子页和非叶子页,由于输出结果行过大(稍微大于表的行数),所以将结果重定向到一个recurseindex.log文件中:
# 表有100w数据,recurse index结果有1003491行数据
[afei@afei mysql]# wc -l recurseindex.log
1003491 recurseindex.log
# 部分内容如下:
[afei@afei mysql]# head -10 recurseindex.log
ROOT NODE #3: 2 records, 26 bytes
NODE POINTER RECORD ≥ (i=1) → #36
INTERNAL NODE #36: 601 records, 7813 bytes
NODE POINTER RECORD ≥ (i=1) → #4
LEAF NODE #4: 287 records, 7462 bytes
RECORD: (i=1) → ()
RECORD: (i=2) → ()
RECORD: (i=3) → ()
RECORD: (i=4) → ()
RECORD: (i=5) → ()
从结果可知,ROOT NODE即根节点是page=3的页。通过space-page-type-regions的分析可知,0,1,2这三个page类型是FSP_HDR, IBUF_BITMAP, INODE。其他的就是INTERNAL节点和LEAF节点。
page-records
统计某一页中的数据。以刚才index-recurse
的结果为例,page=3是root页,这个page的数据如下,由结果可知,page=3有两个record,与recurseindex.log的结果是吻合的(ROOT NODE #3: 2 records, 26 bytes):
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/t_afei -p 3 page-records > 3.log
[afei@afei mysql]# cat 3.log
Record 125: (id=1) → #36
Record 138: (id=344688) → #37
从这个结果也能看出来,root页是不保存具体数据,只保存主键索引的值和指针。
我们再从recurseindex.log中找几个LEAF节点,如下所示,page=1770,1771这些都是LEAF节点:
[afei@afei mysql]# grep "LEAF" recurseindex.log | tail -5
LEAF NODE #1770: 574 records, 14924 bytes
LEAF NODE #1771: 574 records, 14924 bytes
LEAF NODE #1772: 574 records, 14924 bytes
LEAF NODE #1773: 574 records, 14924 bytes
LEAF NODE #1774: 379 records, 9854 bytes
其中,page=1770的部分数据如下,一个页大概能574条记录,而page=1774还没有填满,只有379条数据:
[afei@afei mysql]
[afei@afei mysql]
Record 125: (id=997326) → (num=997326)
Record 151: (id=997327) → (num=997327)
Record 177: (id=997328) → (num=997328)
Record 203: (id=997329) → (num=997329)
Record 229: (id=997330) → (num=997330)
... ...
从这个结果也能看出来,叶子页会保存具体数据,不只是主键,非主键其他列(num列)的数据也有保存。
tree height
根据通过innodb_space得到的结果,我们大概能计算出索引树的高度,假设树的高度是h:
百万级数据量的表,574^h=1000000。即h=2.17,所以百万级数据量且主键是int类型的表的索引树高度是3。
十万级数据量的表,574^h=100000。即h=1.81,所以十万级数据量且主键是int类型的表的索引树高度是2。
下面是笔者对一张十万数据量的表做得测试,通过结果可以看出level=0或者1都有结果,level=2没有结果,所以索引树高度是2:
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/tsw_afei -I PRIMARY -l 0 index-level-summary | wc -l
176
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/tsw_afei -I PRIMARY -l 1 index-level-summary
page index level data free records min_key
3 2416 1 2275 13893 175 id=1
[afei@afei mysql]# innodb_space -s ibdata1 -T yyfax_afei/tsw_afei -I PRIMARY -l 2 index-level-summary
page index level data free records min_key
[afei@afei mysql]#